package proj2501.util;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.xml.bind.DatatypeConverter;

public class UU {
	public static String getUTF_8Name() {
//		return StandardCharsets.UTF_8.name(); // After Java 1.7.
		return "UTF-8";
	}
	private static final String DEFAULT_CHARACTER_ENCODING = UU.getUTF_8Name();
	private static String lineSeparator;
	
	/** Returns the system-dependent line separator string. System.lineSeparator() method exists only after Java 1.7. */
	public static String getLineSeparator() {
		if (lineSeparator == null) {
			lineSeparator = System.getProperty("line.separator");
		}
		return lineSeparator;
	}
	
	private static String indent4;
	
	/** Returns four white spaces. */
	public static String getIndent4() {
		if (indent4 == null) {
			indent4 = "    ";
		}
		return indent4;
	}
	
	/** Prints arguments of the program. Put "args" from the main(String[] args). */
	public static void printArgs(String[] args) {
		String lineSeparator = getLineSeparator();
		StringBuilder sb = new StringBuilder();
		sb.append("Printing given arguments: ");
		if (args == null) {
			// This is a rare case.
			sb.append("No arguments. (args == null)");
		} else if (args.length == 0) {
			// This is a very usual case for when no arguments were given.
			sb.append("No arguments. (args.length == 0)");
		} else {
			// Prints each arguments.
			for (int i = 0; i < args.length; i++) {
				sb.append(lineSeparator);
				sb.append(UU.getIndent4() + "[" + i + "]: " + args[i]);
			}
		}
		System.out.println(sb);
	}
	
	public static Object getNewInstance(Class<?> clazz) {
		Constructor<?> constructor;
		try {
			constructor = clazz.getConstructor();
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		} catch (SecurityException e) {
			throw new RuntimeException(e);
		}
//		boolean accessible = constructor.isAccessible();
//		if (accessible == false) {
//			constructor.setAccessible(true);
//		}
		Object object;
		try {
			object = constructor.newInstance();
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
		return object;
	}
	
	public static FileOutputStream getFileOutputStream(File file) {
		FileOutputStream fos;
		try {
			fos = new FileOutputStream(file);
		} catch (FileNotFoundException e) {
			return null;
		}
		return fos;
	}
	
	public static FileOutputStream getFileOutputStream(String path) {
		File file = new File(path);
		return getFileOutputStream(file);
	}
	
	public static OutputStreamWriter getOutputStreamWriter(String path, String encoding) {
		FileOutputStream fos = getFileOutputStream(path);
		if (fos == null) {
			return null;
		}
		boolean failed = false;
		OutputStreamWriter osw = null;
		try {
			osw = new OutputStreamWriter(fos, encoding);
		} catch (UnsupportedEncodingException e1) {
			try {
				fos.close();
			} catch (IOException e2) {
				
			}
			failed = true;
		}
		if (failed) {
			try {
				fos.close();
			} catch (IOException e) {
				
			}
			return null;
		}
		return osw;
	}
	public static OutputStreamWriter getOutputStreamWriter(String path) {
		return getOutputStreamWriter(path, DEFAULT_CHARACTER_ENCODING);
	}
	public static boolean write(String path, String s) {
		OutputStreamWriter osw = getOutputStreamWriter(path);
		if (osw == null) {
			return false;
		}
		try {
			try {
				osw.write(s);
			} catch (IOException e) {
				return false;
			}
			try {
				osw.close();
			} catch (IOException e) {
				return false;
			}
		} finally {
			try {
				osw.close();
			} catch (IOException e) {
				
			}
		}
		return true;
	}
	public static PrintWriter getPrintWriter(String path, String encoding) {
		OutputStreamWriter osw = getOutputStreamWriter(path, encoding);
		if (osw == null) {
			return null;
		}
		BufferedWriter bw = new BufferedWriter(osw);
		PrintWriter pw = new PrintWriter(bw);
		return pw;
	}
	
	public static PrintWriter getPrintWriter(String path) {
		return getPrintWriter(path, DEFAULT_CHARACTER_ENCODING);
	}
	
	public static FileInputStream getFileInputStream(File file) {
		FileInputStream fis;
		try {
			fis = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			return null;
		}
		return fis;
	}
	
	public static BufferedInputStream getBufferedInputStream(File file) {
		FileInputStream fis = getFileInputStream(file);
		if (fis == null) {
			return null;
		}
		return new BufferedInputStream(fis);
	}
	
	public static BufferedReader getBufferedReader(String path, String encoding) {
		File file = new File(path);
		FileInputStream fis;
		try {
			fis = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			return null;
		}
		InputStreamReader isr;
		try {
			isr = new InputStreamReader(fis, encoding);
		} catch (UnsupportedEncodingException e) {
			return null;
		}
		BufferedReader br = new BufferedReader(isr);
		return br;
	}
	
	public static BufferedReader getBufferedReader(String path) {
		return getBufferedReader(path, DEFAULT_CHARACTER_ENCODING);
	}
	
	public static void print(BufferedReader br) {
		try {
			int lineNum = 1;
			while (true) {
				try {
					if (! br.ready()) {
						break;
					}
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
				String line;
				try {
					line = br.readLine();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
				System.out.println("line " + lineNum + ": " + line);
				lineNum++;
			}
			try {
				br.close();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		} finally {
			try {
				br.close();
			} catch (IOException e) {
				
			}
		}
	}
	
	public static String read(String path) {
		BufferedReader br = getBufferedReader(path, DEFAULT_CHARACTER_ENCODING);
		if (br == null) {
			return null;
		}
		StringBuilder sb = new StringBuilder();
		char[] chars = new char[1024];
		int len;
		try {
			while (true) {
				try {
					len = br.read(chars);
				} catch (IOException e) {
					return null;
				}
				if (len == -1) {
					break;
				}
				sb.append(chars, 0, len);
			}
			try {
				br.close();
			} catch (IOException e) {
				return null;
			}
		} finally {
			try {
				br.close();
			} catch (IOException e) {
				
			}
		}
		return sb.toString();
	}
	
	public static boolean zip(String srcPath, String dstPath) {
		File srcFile = new File(srcPath).getAbsoluteFile();
		String basePath = srcFile.getParent() + File.separator;
		FileOutputStream fos = getFileOutputStream(dstPath);
		if (fos == null) {
			return false;
		}
		ZipOutputStream zos;
		zos = new ZipOutputStream(fos);
		boolean result = zip(basePath, zos, srcFile);
		try {
			zos.close();
		} catch (IOException e) {
			result = false;
		}
		return result;
	}
	
	private static boolean zip(String basePath, ZipOutputStream zos, File file) {
		if (file.isDirectory()) {
			File[] files = file.listFiles();
			for (File file2: files) {
				boolean result = zip(basePath, zos, file2);
				if (result == false) {
					return false;
				}
			}
		} else {
			String absPath = file.getAbsolutePath();
			System.out.println("absPath: " + absPath);
			String entryName;
			System.out.println("basePath: " + basePath);
			if (absPath.startsWith(basePath)) {
				entryName = absPath.substring(basePath.length(), absPath.length());
				System.out.println("entryName: " + entryName);
				entryName = "AA\\" + entryName;
			} else {
				throw new RuntimeException();
			}
			ZipEntry entry = new ZipEntry(entryName);
			try {
				zos.putNextEntry(entry);
			} catch (IOException e) {
				return false;
			}
			BufferedInputStream bis = getBufferedInputStream(file);
			if (bis == null) {
				return false;
			}
			boolean failed = false;
			byte[] bytes = new byte[1024];
			int len;
			while (true) {
				try {
					len = bis.read(bytes);
				} catch (IOException e) {
					failed = true;
					break;
				}
				if (len == -1) {
					break;
				}
				try {
					zos.write(bytes, 0, len);
				} catch (IOException e) {
					failed = true;
					break;
				}
			}
			try {
				bis.close();
			} catch (IOException e) {
				failed = true;
			}
			if (failed) {
				return false;
			}
		}
		return true;
	}
	
	public static byte[] getMessageDigest(String algorithm, byte[] bytes, int offset, int len) {
		MessageDigest digest;
		try {
			digest = MessageDigest.getInstance(algorithm);
		} catch (NoSuchAlgorithmException e) {
			return null;
		}
		digest.update(bytes, offset, len);
		return digest.digest();
	}
	
	public static byte[] getMessageDigest(String algorithm, byte[] bytes) {
		return getMessageDigest(algorithm, bytes, 0, bytes.length);
	}
	
	public static String getMessageDigestString(String algorithm, byte[] bytes) {
		byte[] digest = UU.getMessageDigest(algorithm, bytes);
		if (digest == null) {
			return null;
		}
		return DatatypeConverter.printHexBinary(digest);
	}
	
	public static boolean printMessageDigest(String algorithm, byte[] bytes) {
		String s = getMessageDigestString(algorithm, bytes);
		if (s == null) {
			return false;
		}
		System.out.println(s);
		return true;
	}
	
	public static byte[] getMessageDigest(String algorithm, InputStream is) {
		MessageDigest digest;
		try {
			digest = MessageDigest.getInstance(algorithm);
		} catch (NoSuchAlgorithmException e) {
			return null;
		}
		
		byte[] bytes = new byte[1024];
		int len;
		boolean failed = false;
		while (true) {
			try {
				len = is.read(bytes);
			} catch (IOException e) {
				failed = true;
				break;
			}
			if (len == -1) {
				break;
			}
			digest.update(bytes, 0, len);
		}
		try {
			is.close();
		} catch (IOException e) {
			failed = true;
		}
		if (failed) {
			return null;
		}
		return digest.digest();
	}
	
	public static byte[] getMessageDigest(String algorithm, File file) {
		BufferedInputStream bis = getBufferedInputStream(file);
		if (bis == null) {
			return null;
		}
		return getMessageDigest(algorithm, bis);
	}
	
	public static String getMessageDigestString(String algorithm, File file) {
		byte[] digest = UU.getMessageDigest(algorithm, file);
		if (digest == null) {
			return null;
		}
		return DatatypeConverter.printHexBinary(digest);
	}
	
	public static boolean printMessageDigest(String algorithm, File file) {
		String s = getMessageDigestString(algorithm, file);
		if (s == null) {
			return false;
		}
		System.out.println(s);
		return true;
	}
	
	public static byte[] getMD5(byte[] bytes, int offset, int len) {
		return getMessageDigest("MD5", bytes, offset, len);
	}
	
	public static byte[] getMD5(byte[] bytes) {
		return getMessageDigest("MD5", bytes);
	}
	
	public static boolean printMD5(byte[] bytes) {
		return printMessageDigest("MD5", bytes);
	}
	
	public static byte[] getMD5(File file) {
		return getMessageDigest("MD5", file);
	}
	
	public static String getMD5String(File file) {
		return getMessageDigestString("MD5", file);
	}
	
	public static boolean printMD5(File file) {
		return printMessageDigest("MD5", file);
	}
	
	public static byte[] getSHA1(byte[] bytes, int offset, int len) {
		return getMessageDigest("SHA-1", bytes, offset, len);
	}
	
	public static byte[] getSHA1(byte[] bytes) {
		return getMessageDigest("SHA-1", bytes, 0, bytes.length);
	}
	
	public static boolean printSHA1(byte[] bytes) {
		return printMessageDigest("MD5", bytes);
	}
	
	public static String getSHA1String(File file) {
		return getMessageDigestString("SHA-1", file);
	}
}
